package com.hanxiaozhang.http.netty;

import com.alibaba.fastjson.JSONObject;
import com.hanxiaozhang.http.config.Context;
import com.hanxiaozhang.http.constant.ContextConstant;
import com.hanxiaozhang.http.dispatcher.Dispatcher;
import com.hanxiaozhang.http.dispatcher.ResponseModel;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpHeaderNames.*;

/**
 * 〈一句话功能简述〉<br>
 * 〈〉
 *
 * @author hanxiaozhang
 * @create 2022/2/1
 * @since 1.0.0
 */
@ChannelHandler.Sharable
public class HttpRequestHandler extends SimpleChannelInboundHandler<HttpObject> {

    private final static Logger logger = Logger.getLogger(HttpRequestHandler.class);

    private Dispatcher dispatcher;

    @Override
    public void channelRead0(ChannelHandlerContext ctx, HttpObject msg)
            throws Exception {
        parseQuery(ctx, msg);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (ctx.channel().isOpen()) {
            ctx.close();
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (IdleStateEvent.class.isAssignableFrom(evt.getClass())) {
            ctx.close();
        }
    }

    /**
     * 解析请求
     *
     * @param ctx
     * @param message
     * @throws Exception
     */
    private void parseQuery(ChannelHandlerContext ctx, HttpObject message) throws Exception {

        try {
            if (message instanceof HttpRequest) {
                Context context = new Context();
                HttpRequest request = (HttpRequest) message;
                context.put("request", request);

                logger.debug("httpMethod is " + request.method().toString());

                if (request.method() == HttpMethod.GET || request.method() == HttpMethod.HEAD) {

                    parseQueryParameters(request.uri(), context);
                    ResponseModel res = (ResponseModel) dispatcher.dispatch(context);
                    writeResponse(ctx.channel(), res, request);
                } else if (request.method() == HttpMethod.POST) {

                    HttpContent chunk = (HttpContent) message;
                    if (chunk instanceof LastHttpContent) {
                        ByteBuf buf = chunk.content();
                        String data = buf.toString(CharsetUtil.UTF_8);

                        buf.clear();

                        String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE);
                        if ((null != contentType) && (contentType.toLowerCase().startsWith("application/json"))) {
                            Object object = JSONObject.parse(data);
                            if (object != null) {
                                context.put("data", object);
                            }
                            parseQueryParameters(request.uri(), context);
                        } else {
                            String uri = request.uri();
                            if (null != data && !"".equals(data)) {
                                if (uri.contains("?")) {
                                    uri = uri + "&" + data;
                                } else {
                                    uri = uri + "?" + data;
                                }
                            }
                            parseQueryParameters(uri, context);
                        }
                        ResponseModel res = (ResponseModel) dispatcher.dispatch(context);
                        writeResponse(ctx.channel(), res, request);
                    }
                } else {

                    parseQueryParameters(request.uri(), context);
                    ResponseModel res = (ResponseModel) dispatcher.dispatch(context);
                    writeResponse(ctx.channel(), res, request);
                }
            }
        } catch (Exception e) {
            throw e;
        }
    }

    /**
     * 解析请求参数
     *
     * @param uri
     * @param context
     */
    private void parseQueryParameters(String uri, Context context) {

        QueryStringDecoder decoderQuery = new QueryStringDecoder(uri);
        HttpRequest request = (HttpRequest) context.getObject("request");
        String key = StringUtils.strip(decoderQuery.path() + "/$" + request.method(), "/");
        context.put(ContextConstant.CONTROLLER_KEY, key);

        Map<String, List<String>> uriAttributes = decoderQuery.parameters();
        for (Map.Entry<String, List<String>> attr : uriAttributes.entrySet()) {
            for (String attrVal : attr.getValue()) {
                context.put(attr.getKey(), attrVal);
            }
        }
    }

    /**
     * http返回响应数据
     *
     * @param channel
     */
    private void writeResponse(Channel channel, ResponseModel model, HttpRequest request) throws Exception {
        try {
            // 将响应内容转换为ChannelBuffer
            ByteBuf buf = copiedBuffer(model.getContent().toString(), CharsetUtil.UTF_8);

            // 构建响应对象
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, model.getStatus(), buf);
            response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
            response.headers().set(CONTENT_LENGTH, response.content().readableBytes());

            // 添加附加Headers
            Map<String, Object> headers = model.getHeaders();
            if (null != headers && !headers.isEmpty()) {
                Iterator<Map.Entry<String, Object>> iterator = headers.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, Object> en = iterator.next();
                    response.headers().set(en.getKey(), en.getValue());
                }
            }

            // 写响应
            response.headers().set(CONNECTION, HttpHeaderValues.CLOSE);
            channel.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)
                    .addListener(ChannelFutureListener.CLOSE_ON_FAILURE);

            buf.clear();
        } catch (Exception e) {
            throw e;
        }
    }

    public void setDispatcher(Dispatcher dispatcher) {
        this.dispatcher = dispatcher;
    }

}
